use std::fmt::Display;

use crate::{
    error::InvalidInstructionError,
    writer::asm::{AsmConditionalJmp, AsmGenerator, AsmRegistor},
};

/// Represents the type of a command.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommandType {
    Arithmetic,
    Push,
    Pop,
    Label,
    Goto,
    IfGoto,
    Function,
    Return,
    Call,
}

impl TryFrom<String> for CommandType {
    type Error = InvalidInstructionError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        let command_type = match value.as_str() {
            "add" | "sub" | "neg" | "eq" | "gt" | "lt" | "and" | "or" | "not" => {
                CommandType::Arithmetic
            }
            "push" => CommandType::Push,
            "pop" => CommandType::Pop,
            "label" => CommandType::Label,
            "goto" => CommandType::Goto,
            "if-goto" => CommandType::IfGoto,
            "function" => CommandType::Function,
            "return" => CommandType::Return,
            "call" => CommandType::Call,
            _ => return Err(InvalidInstructionError::CommandType(value)),
        };
        Ok(command_type)
    }
}

impl Display for CommandType {
    /// convert into vm command
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let vm_code = match self {
            CommandType::Arithmetic => "",
            CommandType::Push => "push",
            CommandType::Pop => "pop",
            CommandType::Label => "label",
            CommandType::Goto => "goto",
            CommandType::IfGoto => "if-goto",
            CommandType::Function => "function",
            CommandType::Return => "return",
            CommandType::Call => "call",
        };

        write!(f, "{vm_code}")
    }
}
/// Represents a command.
#[derive(Debug, Clone)]
pub(crate) struct Command {
    pub(crate) command_type: CommandType,
    pub(crate) arg1: String,
    pub(crate) arg2: Option<u32>,
    /// the orginal vm code
    pub(crate) vm_code: String,
}

impl TryFrom<String> for Command {
    type Error = InvalidInstructionError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        let mut parts = value.split_whitespace();
        let operator = parts.next().unwrap_or_default();
        let command_type = CommandType::try_from(operator.to_string())?;
        let arg1 = if [CommandType::Arithmetic, CommandType::Return].contains(&command_type) {
            operator
        } else {
            parts
                .next()
                .ok_or(InvalidInstructionError::Argument(value.clone()))?
        }
        .into();

        let arg2 = parts
            .next()
            .map(|s| {
                s.parse()
                    .map_err(|e| InvalidInstructionError::Argument(format!("{value},{e}")))
            })
            .transpose()?;
        Ok(Command {
            command_type,
            arg1,
            arg2,
            vm_code: value,
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Segment {
    Argument,
    Local,
    Static,
    Constant,
    This,
    That,
    Pointer,
    Temp,
}

impl TryFrom<&str> for Segment {
    type Error = InvalidInstructionError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            "argument" => Ok(Self::Argument),
            "local" => Ok(Self::Local),
            "static" => Ok(Self::Static),
            "constant" => Ok(Self::Constant),
            "this" => Ok(Self::This),
            "that" => Ok(Self::That),
            "pointer" => Ok(Self::Pointer),
            "temp" => Ok(Self::Temp),
            _ => Err(InvalidInstructionError::ArgumentValue(
                "Invalid segment name".into(),
            )),
        }
    }
}
impl Display for Segment {
    /// convert into vm command
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let vm_code = match self {
            Segment::Argument => "argument",
            Segment::Local => "local",
            Segment::Static => "static",
            Segment::Constant => "constant",
            Segment::This => "this",
            Segment::That => "that",
            Segment::Pointer => "pointer",
            Segment::Temp => "temp",
        };
        write!(f, "{vm_code}")
    }
}
impl Segment {
    const THIS: &'static str = "3";
    const TEMP: &'static str = "5";
    /// convert segment to asm redefined signal
    ///
    /// # arguments
    /// * vm_file_name: &str - the name of the vm file
    /// * index: u8 - the index of the segment
    pub(crate) fn to_asm_signal(&self, vm_file_name: &str, index: u32) -> String {
        match self {
            Segment::Argument => String::from("ARG"),
            Segment::Local => String::from("LCL"),
            Segment::This => String::from("THIS"),
            Segment::That => String::from("THAT"),
            Segment::Temp => String::from(Self::TEMP),
            Segment::Static => format!("{vm_file_name}.{index}"),
            Segment::Pointer => String::from(Self::THIS),
            Segment::Constant => panic!("The constant can't translated to asm redefined signal"),
        }
    }
}

/// Arithmetic command enum
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ArithmeticCommand {
    Add,
    Sub,
    Neg,
    Eq,
    Gt,
    Lt,
    And,
    Or,
    Not,
}
impl TryFrom<&str> for ArithmeticCommand {
    type Error = InvalidInstructionError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            "add" => Ok(Self::Add),
            "sub" => Ok(Self::Sub),
            "neg" => Ok(Self::Neg),
            "eq" => Ok(Self::Eq),
            "gt" => Ok(Self::Gt),
            "lt" => Ok(Self::Lt),
            "and" => Ok(Self::And),
            "or" => Ok(Self::Or),
            "not" => Ok(Self::Not),
            _ => Err(InvalidInstructionError::Command(
                "Invalid arithmetic command".into(),
            )),
        }
    }
}

impl Display for ArithmeticCommand {
    /// convert into vm command
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let vm_code = match self {
            ArithmeticCommand::Add => "add",
            ArithmeticCommand::Sub => "sub",
            ArithmeticCommand::Neg => "neg",
            ArithmeticCommand::Eq => "eq",
            ArithmeticCommand::Gt => "gt",
            ArithmeticCommand::Lt => "lt",
            ArithmeticCommand::And => "and",
            ArithmeticCommand::Or => "or",
            ArithmeticCommand::Not => "not",
        };

        write!(f, "{vm_code}")
    }
}

impl ArithmeticCommand {
    /// whether the command is comparable
    pub(crate) fn is_comparable(&self) -> bool {
        matches!(
            self,
            ArithmeticCommand::Eq | ArithmeticCommand::Gt | ArithmeticCommand::Lt
        )
    }
    /// translate to assembly code
    ///
    /// # arguments
    /// - `jmp_label`: the label to jump to if the condition is met
    pub(crate) fn to_asm(&self, jmp_label: Option<&str>) -> String {
        match self {
            ArithmeticCommand::Add => AsmGenerator::asm_assignment(
                AsmGenerator::DREG,
                &AsmGenerator::asm_arithmetic_add(AsmGenerator::DREG, AsmGenerator::MREG),
            ),
            ArithmeticCommand::Sub => AsmGenerator::asm_assignment(
                AsmGenerator::DREG,
                &AsmGenerator::asm_arithmetic_sub(AsmGenerator::MREG, AsmGenerator::DREG),
            ),
            ArithmeticCommand::Neg => AsmGenerator::asm_assignment(
                AsmGenerator::DREG,
                &AsmGenerator::asm_arithmetic_neg(AsmGenerator::MREG),
            ),
            ArithmeticCommand::Eq | ArithmeticCommand::Gt | ArithmeticCommand::Lt => {
                self.general_comparison_code(jmp_label.unwrap_or_default())
            }
            ArithmeticCommand::And => AsmGenerator::asm_assignment(
                AsmGenerator::DREG,
                &AsmGenerator::asm_arithmetic_and(AsmGenerator::DREG, AsmGenerator::MREG),
            ),
            ArithmeticCommand::Or => AsmGenerator::asm_assignment(
                AsmGenerator::DREG,
                &AsmGenerator::asm_arithmetic_or(AsmGenerator::DREG, AsmGenerator::MREG),
            ),
            ArithmeticCommand::Not => AsmGenerator::asm_assignment(
                AsmGenerator::DREG,
                &AsmGenerator::asm_arithmetic_not(AsmGenerator::DREG),
            ),
        }
    }
    /// generate assembly code for general comparison commands
    ///
    /// # arguments
    /// - `jmp_label`: the label to jump to
    fn general_comparison_code(&self, jmp_label: &str) -> String {
        let code: Vec<_> = vec![
            AsmGenerator::asm_assignment(
                AsmGenerator::DREG,
                &AsmGenerator::asm_arithmetic_sub(AsmGenerator::MREG, AsmGenerator::DREG),
            ),
            AsmGenerator::asm_assignment(AsmGenerator::MREG, "-1"),
            format!("@{jmp_label}"),
            match self {
                ArithmeticCommand::Eq => {
                    AsmGenerator::asm_conditional_jmp(AsmRegistor::D, AsmConditionalJmp::Jeq)
                }
                ArithmeticCommand::Gt => {
                    AsmGenerator::asm_conditional_jmp(AsmRegistor::D, AsmConditionalJmp::Jgt)
                }
                ArithmeticCommand::Lt => {
                    AsmGenerator::asm_conditional_jmp(AsmRegistor::D, AsmConditionalJmp::Jlt)
                }
                _ => panic!("Invalid comparison command"),
            },
            AsmGenerator::asm_addr_sp(),
            AsmGenerator::asm_assignment(AsmGenerator::AREG, AsmGenerator::MREG),
            AsmGenerator::asm_assignment(AsmGenerator::MREG, "0"),
            format!("({jmp_label})"),
        ];
        code.join("\n")
    }
}
